#include <UnitTest++/UnitTest++.h>
#include <MockItNow/MockItNow.h>

namespace TestClasses
{
	struct Bar
	{
		Bar()
			: value(0.0f)
		{
		}

		float value;
	};

	bool operator==(const Bar& lhs, const Bar& rhs)
	{
		return lhs.value == rhs.value;
	}

	std::ostream& operator<<(std::ostream& stream, const Bar& bar)
	{
		return stream << "Bar(value: " << bar.value << ")";
	}

	class AbstractBase
	{
	public:
		virtual void PureVirtual() = 0;
	};

	class Concrete : public AbstractBase
	{
	public:
		Concrete()
		{
		}

		Concrete(const AbstractBase&)
		{
		}

		virtual void PureVirtual() {}
	};

	bool operator==(const Concrete&, const Concrete&)
	{
		return true;
	}

	std::ostream& operator<<(std::ostream& stream, const Concrete&)
	{
		return stream << "Concrete()";
	}

	class Foo
	{
	public:

		Foo()
		{
		}

		static void StaticFunction()
		{
		}

		virtual void VirtualFunction()
		{
		}

		void NoReturn()
		{
		}

		void NoReturnWithArguments(int, int, float)
		{
		}

		float FloatReturn()
		{
			return 1.0f;
		}

		void Overloaded(int)
		{
		}

		void Overloaded(float)
		{
		}

		void UserType(const Bar&)
		{
		}

		void NoReturnWithOutReferenceArgument(int& a)
		{
			a = 1;
		}

		void NoReturnWithOutPointerArgument(int* a)
		{
			*a = 1;
		}

		void AbstractParameter(AbstractBase&)
		{
		}
	};
}

DECLARE_STORAGE_TYPE(TestClasses::AbstractBase, TestClasses::Concrete);

namespace
{
	using namespace TestClasses;

	void FreeFunction()
	{
	}

	struct Fixture
	{
		Fixture()
		{
		}

		Foo foo;
		Bar bar;
		Mocker mocker;
	};

	TEST_FIXTURE (Fixture, MemberFunction)
	{
		REGISTER(Foo::NoReturn);

		RECORD
		{
			// These are identical
			foo.NoReturn();
			EXPECT(foo.NoReturn());
		}

		foo.NoReturn();
		foo.NoReturn();
	}

	TEST_FIXTURE (Fixture, VirtualFunction)
	{
		REGISTER(Foo::VirtualFunction);

		RECORD
		{
			foo.VirtualFunction();
		}

		foo.VirtualFunction();
	}

	TEST_FIXTURE (Fixture, FreeFunction)
	{
		REGISTER(FreeFunction);

		RECORD
		{
			FreeFunction();
		}

		FreeFunction();
	}

	TEST_FIXTURE (Fixture, StaticFunction)
	{
		REGISTER(Foo::StaticFunction);

		RECORD
		{
			Foo::StaticFunction();
		}

		Foo::StaticFunction();
	}

	TEST_FIXTURE (Fixture, ArgumentChecking)
	{
		REGISTER(Foo::NoReturnWithArguments);

		RECORD
		{
			foo.NoReturnWithArguments(1, 2, 3.4f);
		}

		foo.NoReturnWithArguments(1, 2, 3.4f);
	}

	TEST_FIXTURE (Fixture, ReturnValues)
	{
		REGISTER(Foo::FloatReturn);

		RECORD
		{
			EXPECT_RETURN(foo.FloatReturn(), 2.3f);
		}

		CHECK_EQUAL(2.3f, foo.FloatReturn());
	}

	TEST_FIXTURE (Fixture, DefaultStubs)
	{
		REGISTER_STUB(Foo::FloatReturn, 5.6f);

		// I'm not going to call NoReturn, but it won't cause a failure because there is no expectation
		REGISTER_STUB(Foo::NoReturn);

		RECORD
		{
			EXPECT_RETURN(foo.FloatReturn(), 2.3f);
		}

		// This is the expectation return value set up in the record block
		CHECK_EQUAL(2.3f, foo.FloatReturn());	

		// All subsequent calls will use the stub return value provided at register time
		CHECK_EQUAL(5.6f, foo.FloatReturn());
		CHECK_EQUAL(5.6f, foo.FloatReturn());
	}

	TEST_FIXTURE (Fixture, OverloadedFunctions)
	{
		REGISTER_OVERLOADED(Foo::Overloaded, void (Foo::*)(int));

		RECORD
		{
			foo.Overloaded(1);

			// We never registered this overload, so the mocker won't set up and expectation for it.
			// This means it will call the original function.
			foo.Overloaded(4.5f);	
		}

		foo.Overloaded(1);
	}

	TEST_FIXTURE (Fixture, OutReferenceArguments)
	{
		REGISTER(Foo::NoReturnWithOutReferenceArgument);

		int a = 0;

		RECORD
		{
			EXPECT(foo.NoReturnWithOutReferenceArgument(a)).ReturnArgument(Argument1, 12345);
		}

		// This one will use the expectation from the record block
		foo.NoReturnWithOutReferenceArgument(a);
		CHECK_EQUAL(12345, a);

		// This one will use the original function
		foo.NoReturnWithOutReferenceArgument(a);
		CHECK_EQUAL(1, a);
	}

	TEST_FIXTURE (Fixture, OutPointerArguments)
	{
		REGISTER(Foo::NoReturnWithOutPointerArgument);

		int a = 0;
		int result = 12345;

		RECORD
		{
			EXPECT(foo.NoReturnWithOutPointerArgument(&a)).ReturnArgument(Argument1, &result);
		}

		// This one will use the expectation from the record block
		foo.NoReturnWithOutPointerArgument(&a);
		CHECK_EQUAL(12345, a);

		// This one will use the original function
		foo.NoReturnWithOutPointerArgument(&a);
		CHECK_EQUAL(1, a);
	}

	TEST_FIXTURE (Fixture, IgnoreArguments)
	{
		REGISTER(Foo::NoReturnWithArguments);

		RECORD
		{
			EXPECT(foo.NoReturnWithArguments(1, 2, 3.4f)).Ignore(AllArguments);
			EXPECT(foo.NoReturnWithArguments(1, 2, 3.4f)).Ignore(Argument1);
		}

		// Arguments don't matter here
		foo.NoReturnWithArguments(-1, -1, -1);

		// This one is only ignoring the first argument
		foo.NoReturnWithArguments(-1, 2, 3.4f);
	}

	TEST_FIXTURE (Fixture, IgnoreThis)
	{
		REGISTER(Foo::NoReturn);

		RECORD
		{
			EXPECT(foo.NoReturn()).Ignore(This);
		}

		Foo foo2;
		foo2.NoReturn();
	}

	TEST_FIXTURE (Fixture, FailWhenCalled)
	{
		REGISTER(Foo::NoReturnWithArguments);

		RECORD
		{
			// These are the same
			EXPECT(foo.NoReturnWithArguments(1, 2, 3.4f)).FailWhenCalled();
			FAIL_WHEN_CALLED(foo.NoReturnWithArguments(1, 2, 3.4f));
		}

		// Uncomment to see the unit test fail. Note that arguments are ignored when using FailWhenCalled
		//foo.NoReturnWithArguments(-1, -1, -1);
	}

	TEST_FIXTURE (Fixture, CallOriginal)
	{
		REGISTER(Foo::NoReturn);

		RECORD
		{
			EXPECT(foo.NoReturn()).CallOriginal();
		}

		// There's still an expectation that the funciton will be called, but it will use the original implementation
		foo.NoReturn();
	}

	TEST_FIXTURE (Fixture, ReturnAlways)
	{
		REGISTER(Foo::FloatReturn);

		Foo foo2;

		RECORD
		{
			EXPECT_RETURN_ALWAYS(foo.FloatReturn(), 2.3f);
			EXPECT_RETURN_ALWAYS(foo2.FloatReturn(), 4.5f);
		}

		// Note that return always is different than using REGISTER_STUB. REGISTER_STUB will return
		// the same value for all function calls of that type. EXPECT_RETURN_ALWAYS will return the
		// same value for all function calls on that object.

		CHECK_EQUAL(2.3f, foo.FloatReturn());
		CHECK_EQUAL(2.3f, foo.FloatReturn());
		CHECK_EQUAL(2.3f, foo.FloatReturn());

		CHECK_EQUAL(4.5f, foo2.FloatReturn());
		CHECK_EQUAL(4.5f, foo2.FloatReturn());
		CHECK_EQUAL(4.5f, foo2.FloatReturn());
	}

	TEST_FIXTURE (Fixture, UserTypes)
	{
		// As soon as you register this function, you are required to provide operator==
		// and operator<< for ostream. I've done that above.
		REGISTER(Foo::UserType);

		RECORD
		{
			foo.UserType(bar);
		}

		// This works because the compare operator just checks that the contained values are the same
		Bar bar2;
		foo.UserType(bar2);
	}

	TEST_FIXTURE (Fixture, CompareByAddress)
	{
		REGISTER(Foo::UserType);

		RECORD
		{
			EXPECT(foo.UserType(bar)).CompareArgument<Bar>(Argument1, CompareByAddress);
		}

		// This one won't work because the addresses are different
		//Bar bar2;
		//foo.UserType(bar2);

		foo.UserType(bar);
	}

	TEST_FIXTURE (Fixture, CompareByMemory)
	{
		REGISTER(Foo::UserType);

		RECORD
		{
			EXPECT(foo.UserType(bar)).CompareArgument<Bar>(Argument1, CompareByMemory);
		}

		// This only works because the object memory is the same
		Bar bar2;
		foo.UserType(bar2);
	}

	TEST_FIXTURE (Fixture, AbstractParameter)
	{
		REGISTER(Foo::AbstractParameter);

		Concrete concrete;

		RECORD
		{
			foo.AbstractParameter(concrete);
		}

		foo.AbstractParameter(concrete);
	}

	void FunctionThatThrowsException()
	{
		throw 123;
	}

	TEST_FIXTURE (Fixture, CanThrowExceptions)
	{
		REGISTER(FunctionThatThrowsException);

		// First just show that the original exception is being thrown and caught

		bool caught = false;

		try
		{
			FunctionThatThrowsException();
		}
		catch (const int& e)
		{
			CHECK_EQUAL(123, e);
			caught = true;
		}

		CHECK(caught);

		// Now show that we can change the exception inside the record block

		RECORD
		{
			EXPECT_THROW(FunctionThatThrowsException(), 456);
		}

		caught = false;

		try
		{
			FunctionThatThrowsException();
		}
		catch (const int& e)
		{
			CHECK_EQUAL(456, e);
			caught = true;
		}

		CHECK(caught);
	}

	TEST_FIXTURE (Fixture, CanThrowExceptionsByDefault)
	{
		REGISTER_THROW(FunctionThatThrowsException, 456);

		RECORD;

		bool caught = false;

		try
		{
			FunctionThatThrowsException();
		}
		catch (const int& e)
		{
			CHECK_EQUAL(456, e);
			caught = true;
		}

		CHECK(caught);
	}
}